package org.pluslibs.framework.swingplus.jtable;

import java.awt.Dimension;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;

import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;

import org.apache.commons.lang.StringUtils;
import org.pluslibs.framework.swingplus.jpanel.JPanelGrid;
import org.pluslibs.framework.swingplus.utils.KeyEventUtils;

public class JTablePlus extends JPanelGrid {
    private static final Dimension DEFAULT_INITIAL_DIMENSION = new Dimension(250, 80);
    private static final int DEFAULT_INITIAL_ROWS = 1;
    private static final long serialVersionUID = 7683501426980186156L;
    private final DefaultTableModel model;
    private final JTable table;
    private final int minRowsDisplayed;

    public JTablePlus(String... columns) {
        this(DEFAULT_INITIAL_ROWS, DEFAULT_INITIAL_DIMENSION, columns);
    }

    public JTablePlus(int intialRows, String... columns) {
        this(intialRows, DEFAULT_INITIAL_DIMENSION, columns);

    }

    public JTablePlus(int intialRows, Dimension dimension, String... columns) {
        super();
        model = new DefaultTableModel(columns, intialRows);
        table = new JTable(model);
        table.setPreferredScrollableViewportSize(dimension);
        table.setFillsViewportHeight(true);
        table.setRowSelectionAllowed(true);
        table.addKeyListener(new JInputTableListener());
        table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
        minRowsDisplayed = intialRows;
        JScrollPane scrollPane = new JScrollPane(table);
        addRow(scrollPane);
    }

    public void addTableListener(TableModelListener actionsTableListener) {
        model.addTableModelListener(actionsTableListener);
    }

    public String[][] getRows() {
        Vector dataVector = model.getDataVector();
        int columnCount = model.getColumnCount();
        int rowCount = model.getRowCount();

        String[][] rows = new String[rowCount][columnCount];
        for (int i = 0; i < rowCount; ++i) {
            Vector row = (Vector) dataVector.get(i);
            for (int j = 0; j < columnCount; ++j) {
                rows[i][j] = (String) row.get(j);
            }
        }
        return rows;
    }

    public void replaceRows(boolean displayMinRows, String[][] rows) {
        removeRows(displayMinRows);
        addRows(rows);
    }

    public void addRows(String[][] rows) {
        for (int i = 0; i < rows.length; i++) {
            model.addRow(rows[i]);
        }
    }

    private class JInputTableListener implements KeyListener {
        private static final int NO_ROW_SELECTED = -1;
        private static final String EMPTY_CELL = "";

        private boolean addNewRow(KeyEvent e) {
            boolean rowAdded = false;
            if (e.getKeyCode() == KeyEvent.VK_DOWN) {
                if (table.getSelectedRow() == table.getRowCount() - 1) {
                    model.addRow(new String[] { EMPTY_CELL, EMPTY_CELL });
                }
                rowAdded = true;
            }
            return rowAdded;
        }

        private boolean removeRow(KeyEvent e) {
            boolean succesfullyRemoved = false;
            if (e.getKeyCode() == KeyEvent.VK_DELETE) {
                int selectedRow = getSelectedRow();
                boolean cellsEmpty = isRowEmpty(selectedRow);
                if (cellsEmpty && !table.isEditing() && (selectedRow != NO_ROW_SELECTED)) {
                    model.removeRow(selectedRow);
                    model.fireTableStructureChanged();
                    selectPreviousRow(selectedRow);
                    succesfullyRemoved = true;
                }
            }
            return succesfullyRemoved;
        }

        private void selectPreviousRow(int selectedRow) {
            int previousRow = selectedRow;
            if (previousRow >= table.getRowCount()) {
                previousRow = selectedRow - 1;
            }
            if (previousRow > NO_ROW_SELECTED) {
                table.setRowSelectionInterval(previousRow, previousRow);
                // TODO Get Working
                table.editCellAt(previousRow, getSelectedColumn());
            }
        }

        private int getSelectedRow() {
            int selectedRow = table.getSelectedRow();
            if (selectedRow == NO_ROW_SELECTED) {
                selectedRow = table.getRowCount() - 1;
            }
            return selectedRow;
        }

        private boolean isRowEmpty(int selectedRow) {
            if (selectedRow == NO_ROW_SELECTED) {
                return true;
            }
            for (int i = 0; i < table.getColumnCount(); i++) {
                if (StringUtils.isNotEmpty((String) table.getValueAt(selectedRow, i))) {
                    return false;
                }
            }
            return true;
        }

        // KeyListener
        @Override
        public void keyTyped(KeyEvent e) {}

        @Override
        public void keyPressed(KeyEvent e) {
            System.out.println(e.getKeyChar() + ": " + e.getKeyCode());
            if (addNewRow(e)) {
                return;
            }
            if (!removeRow(e)) {
                overwriteCell(e);
            }
        }

        private void overwriteCell(KeyEvent e) {
            int keyCode = e.getKeyCode();
            if (isTableEditKey(keyCode)) {
                int selectedRow = getSelectedRow();
                int selectedColumn = getSelectedColumn();
                if ((selectedRow == NO_ROW_SELECTED) && (e.getKeyCode() != KeyEvent.VK_DELETE)) {
                    addBlankRow();
                    selectedRow = getSelectedRow();
                    table.editCellAt(selectedRow, selectedColumn);
                }
                if (!table.isEditing() && (selectedRow >= NO_ROW_SELECTED + 1)) {
                    model.setValueAt("", selectedRow, selectedColumn);
                }
            }
        }

        private int getSelectedColumn() {
            int selectedColumn = table.getSelectedColumn();
            if (selectedColumn == -1) {
                selectedColumn = 0;
            }
            return selectedColumn;
        }

        @Override
        public void keyReleased(KeyEvent e) {}

    }

    public void clearRows() {
        for (int i = 0; i < model.getRowCount(); i++) {
            clearRow(i);
        }
    }

    private void clearRow(int row) {
        for (int j = 0; j < model.getColumnCount(); j++) {
            model.setValueAt("", row, j);
        }
    }

    public void removeRow(int row, boolean displayMinRows) {
        if ((model.getRowCount() <= minRowsDisplayed) && displayMinRows) {
            clearRow(row);
            displayMinRows();
        } else {
            model.removeRow(row);
        }
    }

    public void removeRows(boolean displayMinRows) {
        int rowCount = model.getRowCount();
        for (int i = rowCount - 1; i >= 0; i--) {
            model.removeRow(i);
        }
        if (displayMinRows) {
            displayMinRows();
        }
    }

    private void displayMinRows() {
        int rowCount = model.getRowCount();
        for (int j = rowCount; j < minRowsDisplayed; ++j) {
            addBlankRow();
        }
    }

    private void addBlankRow() {
        String[] blankRow = new String[model.getColumnCount()];
        model.addRow(blankRow);
    }

    public void removeRows() {
        removeRows(true);
    }

    public int getMinRowsDisplayed() {
        return minRowsDisplayed;
    }

    @Override
    public void setEnabled(boolean enabled) {
        table.setEnabled(enabled);
        table.setVisible(enabled);
        super.setEnabled(enabled);
    }

    private boolean isTableEditKey(int keyCode) {
        return KeyEventUtils.isAlphaNumericSymbolic(keyCode) || KeyEventUtils.isNumKey(keyCode) || (keyCode == KeyEvent.VK_SPACE)
                || (keyCode == KeyEvent.VK_BACK_SPACE) || (keyCode == KeyEvent.VK_DELETE);
    }

    public void setEditable(boolean isEditable) {
        // TODO set all the cells so that any changes made wont be committed but
        // can still select text
    }

    public int getSelectedRow() {
        return table.getSelectedRow();
    }

    public int[] getSelectedRows() {
        return table.getSelectedRows();
    }
}
